package modularity.passiveDrift;

import ec.util.MersenneTwisterFast;

public class BasicMutator implements Mutator {
	
	private double addConnectionProb;
	private double removeConnectionProb;
	private double addNodeProb;
	
	private static final int MINIMUM_COMPLEXITY = 1;
	
	protected static int nodeId;
	
	public BasicMutator(double removeConnectionProb, double addConnectionProb, double addNodeProb){
		this.removeConnectionProb = removeConnectionProb;
		this.addConnectionProb = addConnectionProb;
		this.addNodeProb = addNodeProb;
	}
	
	public static void setNodeId(int value){
		nodeId = value;
	}
	
	public static synchronized int incrementNodeId(){
		nodeId++;
		return nodeId;
	}
	
	public void mutate(Genome<ConnectionGene> gen, MersenneTwisterFast random){
		BasicGenome genome = (BasicGenome) gen;
		
		removeConnectionMutation(genome, random);
		addConnectionMutation(genome, random);
		addNodeMutation(genome, random);
	}

	protected void addConnectionMutation(BasicGenome genome, MersenneTwisterFast random) {

		double prob = random.nextDouble();
		if(prob < addConnectionProb && canAddConnectionGene(genome)){

			int numberOfNodes = genome.getNumberOfNodes();
			int node1 = genome.getNodeAt(random.nextInt(numberOfNodes));

			int node2 = genome.getNodeAt(random.nextInt(numberOfNodes));
			//we do not want self-recurrent connections
			while(node1 == node2){
				node2 = genome.getNodeAt(random.nextInt(numberOfNodes));
			}
			
			ConnectionGene newConnection = new ConnectionGene(node1, node2);
			//check if there is already such a connection in the genome.
			//if there is, discard it. otherwise, insert the new connection.
			if(!genome.containsConnection(newConnection)){
				genome.addConnection(newConnection);
			}
		}
	}

	/**
	 * check if is possible to add a new connection
	 */
	private boolean canAddConnectionGene(BasicGenome genome) {
		int numberOfNodes = genome.getNumberOfNodes();
		int numberOfConnections = genome.getNumberOfConnections();
		/**
		 * An undirected graph with no loops can have at most |V| * (|V| - 1)/2 edges
		 */
		return numberOfConnections < numberOfNodes * (numberOfNodes - 1) / 2;
	}

	protected void addNodeMutation(BasicGenome genome, MersenneTwisterFast random) {
		double prob = random.nextDouble();
		if(prob < addNodeProb){
			int index = random.nextInt(genome.getNumberOfConnections());
			ConnectionGene old = genome.getConnectionAtIndex(index);
			
			//remove the old connection
			genome.removeConnection(index);
			
			//create the two new connections
			int newNode = generateNextNodeId();
			ConnectionGene precedent = new ConnectionGene(old.getFromGene(), newNode);
			ConnectionGene next = new ConnectionGene(newNode, old.getToGene());
			
			//add the new connections
			genome.addConnection(precedent);
			genome.addConnection(next);
		}

	}

	protected void removeConnectionMutation(BasicGenome genome, MersenneTwisterFast random) {
		double prob = random.nextDouble();
		if(prob < removeConnectionProb && genome.getNumberOfConnections() > MINIMUM_COMPLEXITY){
			int index = random.nextInt(genome.getNumberOfConnections());
			genome.removeConnection(index);
		}		
	}

	public Integer generateNextNodeId() {
		int value = incrementNodeId();
		return value;
	}
}